﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Web;
using MetaCollection.Framework.Model;
using MetaCollection.Framework.Security;

namespace MetaCollection.ContentService.Http
{
	/// <summary>
	/// Http 1.1 Methods (including WEBDAV)
	/// </summary>
	[Flags]
	public enum HttpMethods
	{
		/// <summary>
		/// Unknown (none) method
		/// </summary>
		[RequiredAccessRights(AccessRights.None)]
		None = 0,
		/// <summary>
		/// Asks for the response identical to the one that would correspond to a GET request, but without the
		/// response body. This is useful for retrieving meta-information written in response headers, without
		/// having to transport the entire content.
		/// </summary>
		[RequiredAccessRights(AccessRights.ReadProperties)]
		Head = 1,
		/// <summary>
		/// Requests a representation of the specified resource. Note that GET should not be used for operations
		/// that cause side-effects, such as using it for taking actions in web applications. One reason for this
		/// is that GET may be used arbitrarily by robots or crawlers, which should not need to consider the side
		/// effects that a request should cause. See safe methods below.
		/// </summary>
 		[RequiredAccessRights(AccessRights.ReadProperties | AccessRights.ReadResource)]
		Get = 1 << 1,
		/// <summary>
		/// Submits data to be processed (e.g., from an HTML form) to the identified resource. The data is included
		/// in the body of the request. This may result in the creation of a new resource or the updates of
		/// existing resources or both.
		/// </summary>
		[RequiredAccessRights(AccessRights.Unknown)]
		Post = 1 << 2,
		/// <summary>
		/// Uploads a representation of the specified resource.
		/// </summary>
		[RequiredAccessRights(AccessRights.Append, ItemTargets.Parent)]
		Put = 1 << 3,
		/// <summary>
		/// Deletes the specified resource.
		/// </summary>
		[RequiredAccessRights(AccessRights.DeleteItem, ItemTargets.Self | ItemTargets.Children)]
		Delete = 1 << 4,
		/// <summary>
		/// Echoes back the received request, so that a client can see what intermediate servers are adding or
		/// changing in the request.
		/// </summary>
		[RequiredAccessRights(AccessRights.None)]
		Trace = 1 << 5,
		/// <summary>
		/// Returns the HTTP methods that the server supports for specified URL. This can be used to check the
		/// functionality of a web server by requesting '*' instead of a specific resource.
		/// </summary>
		/// <example>
		/// Example Request
		/// 
		/// OPTIONS /default.htm HTTP/1.1
		/// Host: mt.gov
		/// 
		/// Example Response
		/// 
		/// HTTP/1.1 200 OK
		/// Cache-Control: private
		/// Allow: OPTIONS,POST
		/// Server: Microsoft-IIS/7.0
		/// Public: OPTIONS,POST
		/// Date: Thu, 06 AUG 2009 18:49:29 GMT
		/// Content-Length: 0
		/// 
		/// </example>
		[RequiredAccessRights(AccessRights.ReadProperties)]
		Options = 1 << 6,
		/// <summary>
		/// Converts the request connection to a transparent TCP/IP tunnel, usually to facilitate SSL-encrypted
		/// communication (HTTPS) through an unencrypted HTTP proxy.[5]
		/// </summary>
		[RequiredAccessRights(AccessRights.None)]
		Connect = 1 << 7,

		/// <summary>
		/// Used to retrieve properties, stored as XML, from a resource. It is also overloaded to allow one to
		/// retrieve the collection structure (a.k.a. directory hierarchy) of a remote system.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		/// <example>http://msdn.microsoft.com/en-us/library/aa580336.aspx</example>
		[RequiredAccessRights(AccessRights.ReadProperties)]
		PropFind = 1 << 8,
		/// <summary>
		/// Used to change and delete multiple properties on a resource in a single atomic act.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		[RequiredAccessRights(AccessRights.WriteProperties)]
		PropPatch = 1 << 9,
		/// <summary>
		/// Used to create collections (a.k.a. directory).
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		[RequiredAccessRights(AccessRights.Append, ItemTargets.Parent)]
		MKCol = 1 << 10,
		/// <summary>
		/// Used to copy a resource from one URI to another.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		[RequiredAccessRights(AccessRights.AllReadType)]
		Copy = 1 << 11,
		/// <summary>
		/// Used to move a resource from one URI to another.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		/// <example>
		/// 
		/// Example Response.
		/// 
		/// MOVE /parent-folder/child1/ HTTP/1.1
		/// Destination: http://www.mt.gov/parent-folder/child2/
		/// Host: www.mt.gov
		/// 
		/// See http://msdn.microsoft.com/en-us/library/aa563490.aspx
		/// </example>
		[RequiredAccessRights(AccessRights.AllReadType | AccessRights.AllWriteType)]
		Move = 1 << 12,
		/// <summary>
		/// Used to put a lock on a resource. WebDAV supports both shared and exclusive locks.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		[RequiredAccessRights(AccessRights.FullControl)]
		Lock = 1 << 13,
		/// <summary>
		/// To remove a lock from a resource.
		/// </summary>
		/// <remarks>(WEBDAV RFC 2518)</remarks>
		[RequiredAccessRights(AccessRights.FullControl)]
		Unlock = 1 << 14,
		/// <summary>
		/// A request with a SQL payload in a WEBDAV xml package
		/// </summary>
		/// <example>
		/// Samle Request (values in braces are optional, actual request is not HTML encoded)
		/// 
		/// SEARCH /vroot/folder/ HTTP/1.1
		/// Content-Type: text/xml
		/// Host: www.example.com
		/// 
		/// &lt;?xml version="1.0"?&gt;
		/// &lt;D:searchrequest xmlns:D = "DAV:"&gt;
		///   &lt;D:sql&gt;
		///    SELECT {NS:LIST-OF-PROPERTIES | * }
		///    FROM {ITEMURI}
		///    { WHERE NS:PROP=Value }
		///   &lt;/D:sql>
		/// &lt;/D:searchrequest&gt; 
		/// 
		/// Example Response
		/// 
		/// HTTP/1.1 207 Multi-Status
		/// Content-Type: text/xml; charset="utf-8"
		/// Content-Length: 429
		/// 
		/// &lt;?xml version="1.0" encoding="UTF-8"?&gt;
		/// &lt;D:multistatus xmlns:D="DAV:"
		///    xmlns:R="uri://MetaCollection/v2/"&gt;
		///   &lt;D:response&gt;
		///     &lt;D:href&gt;http://mt.gov/item/&lt;/D:href&gt;
		//      &lt;D:status&gt;HTTP/1.1 200 OK&lt;/D:status&gt;
		///   &lt;/D:response&gt;
		/// &lt;/D:multistatus&gt;
		/// 
		///</example>
		[RequiredAccessRights(AccessRights.ReadProperties | AccessRights.ReadResource, ItemTargets.Self | ItemTargets.Children)]
		Search = 1 << 15,
		/// <summary>
		/// A request to modify an items ACL.
		/// See http://www.ietf.org/rfc/rfc3744.txt
		/// </summary>
		[RequiredAccessRights(AccessRights.ReadItemClaims | AccessRights.WriteItemClaims)]
		Acl = 1 << 16,
		/// <summary>
		/// A CHECKOUT request can be applied to a checked-in version-controlled resource to allow modifications
		/// to the content and dead properties of that version-controlled resource.
		/// See http://www.ietf.org/rfc/rfc3253.txt
		/// </summary>
		[RequiredAccessRights(AccessRights.WriteResource | AccessRights.WriteProperties)]
		CheckOut = 1 << 17,
		/// <summary>
		/// A CHECKIN request can be applied to a checked-out version-controlled resource to produce a new version
		/// whose content and dead properties are copied from the checked-out resource.
		/// See http://www.ietf.org/rfc/rfc3253.txt
		/// </summary>
		[RequiredAccessRights(AccessRights.WriteResource | AccessRights.WriteProperties)]
		CheckIn = 1 << 18,
		/// <summary>
		/// A REPORT request is an extensible mechanism for obtaining information about a resource. Unlike a resource
		/// property, which has a single value, the value of a report can depend on additional information specified
		/// in the REPORT request body and in the REPORT request headers.
		/// </summary>
		/// <remarks>Required permissions for this method depend on information requested but due to the expense of
		/// creating a report full read access is required.</remarks>
		[RequiredAccessRights(AccessRights.ReadProperties | AccessRights.ReadResource, ItemTargets.Self | ItemTargets.Children)]
		Report = 1 << 19,
		/// <summary>
		/// A VERSION-CONTROL request can be used to create a version-controlled resource at the request-URL.
		/// See http://www.ietf.org/rfc/rfc3253.txt
		/// </summary>
		/// <remarks>Because every document in the MetaCollection is under version control automatically this
		/// method really does nothing at all other than exist for compatibility with RFC 3252 and to enable 
		/// the version control headers in the options request if the service is configured to support it.</remarks>
		[RequiredAccessRights(AccessRights.WriteResource | AccessRights.WriteProperties)]
		VersionControl = 1 << 20,
		/// <summary>
		/// The MERGE method performs the logical merge of a specified version (the "merge source") into a specified
		/// version-controlled resource (the "merge target").
		/// See http://www.ietf.org/rfc/rfc3253.txt
		/// </summary>
		/// <remarks>Upto two sources are supported by the Content Service.</remarks>
		[RequiredAccessRights(AccessRights.WriteResource | AccessRights.WriteProperties)]
		Merge = 1 << 21
	}

	public static class MethodsExtensions
	{
		/// <summary>
		/// Returns the Http Method used in the HttpRequest.
		/// </summary>
		/// <param name="request">The request.</param>
		/// <returns>The Http Method for the Request.</returns>
		public static HttpMethods Method(this HttpRequest request)
		{
			try
			{
				string requestMethod = request.RequestType;
				// fix for method name that is invalid in C#.
				if (requestMethod.Equals("VERSION-CONTROL", StringComparison.OrdinalIgnoreCase))
					requestMethod = "VersionControl";

				return (HttpMethods)Enum.Parse(typeof(HttpMethods), requestMethod, true);
			}
			catch (ArgumentException)
			{
				return HttpMethods.None;
			}
		}

		/// <summary>
		/// Returns the Http Method used in the HttpRequest.
		/// </summary>
		/// <param name="requestMethod">The request method.</param>
		/// <returns>The Http Method for the Request.</returns>
		public static HttpMethods ParseHttpMethod(this string requestMethod)
		{
			try
			{
				// fix for method name that is invalid in C#.
				if (requestMethod.Equals("VERSION-CONTROL", StringComparison.OrdinalIgnoreCase))
					requestMethod = "VersionControl";

				return (HttpMethods)Enum.Parse(typeof(HttpMethods), requestMethod, true);
			}
			catch (ArgumentException)
			{
				return HttpMethods.None;
			}
		}

		/// <summary>
		/// Demands the item rights as defined on the Http.Methods
		/// </summary>
		/// <param name="method">The method.</param>
		/// <param name="userPermissions">The collection user permissions.</param>
		/// <param name="item">The item.</param>
		/// <exception cref="SecurityException">thrown in rights are not satisfied for the user</exception>
		public static void DemandItemRights(this HttpMethods method, MetaPrincipal userPermissions, IItemInstance item)
		{
			IDictionary<ItemTargets, AccessRights> rightSet = RequiredAccessRights.GetRightsOnField(typeof(HttpMethods), method.ToString());
			foreach (var record in rightSet)
			{
				if ((record.Key & ItemTargets.Self) == ItemTargets.Self)
				{
					if ((record.Value & AccessRights.Unknown) == AccessRights.Unknown)
						throw new InvalidOperationException("It is not possible to determine if the user possesses an Unknown right.");
					item.DemandAccess(userPermissions, record.Value);
				}
			}
		}

		/// <summary>
		/// Demands the parent item rights as defined on the Http.Methods
		/// </summary>
		/// <param name="method">The method.</param>
		/// <param name="userPermissions">The collection user permissions.</param>
		/// <param name="itemParent">The item parent.</param>
		/// <exception cref="SecurityException">thrown in rights are not satisfied for the user</exception>
		public static void DemandParentRights(this HttpMethods method, MetaPrincipal userPermissions, IItemInstance itemParent)
		{
			IDictionary<ItemTargets, AccessRights> rightSet = RequiredAccessRights.GetRightsOnField(typeof(HttpMethods), method.ToString());
			foreach (var record in rightSet)
			{
				if ((record.Key & ItemTargets.Parent) == ItemTargets.Self)
				{
					if ((record.Value & AccessRights.Unknown) == AccessRights.Unknown)
						throw new InvalidOperationException("It is not possible to determine if the user possesses an Unknown right.");
					itemParent.DemandAccess(userPermissions, record.Value);
				}
			}
		}

		/// <summary>
		/// Demands the children rights as defined on the Http.Methods
		/// </summary>
		/// <param name="method">The method.</param>
		/// <param name="userPermissions">The collection user permissions.</param>
		/// <param name="children">The children.</param>
		/// <exception cref="SecurityException">thrown in rights are not satisfied for the user</exception>
		public static void DemandChildrenRights(this HttpMethods method, MetaPrincipal userPermissions, IEnumerable<IItemInstance> children)
		{
			IDictionary<ItemTargets, AccessRights> rightSet = RequiredAccessRights.GetRightsOnField(typeof(HttpMethods), method.ToString());
			foreach (var record in rightSet)
			{
				if ((record.Key & ItemTargets.Children) == ItemTargets.Self)
				{
					foreach (IItemInstance child in children)
					{
						if ((record.Value & AccessRights.Unknown) == AccessRights.Unknown)
							throw new InvalidOperationException("It is not possible to determine if the user possesses an Unknown right.");
						child.DemandAccess(userPermissions, record.Value);
					}
				}
			}
		}

		/// <summary>
		/// Determines whether the specified method is set.
		/// </summary>
		/// <param name="methods">The methods.</param>
		/// <param name="method">The method.</param>
		/// <returns>
		/// 	<c>true</c> if the specified methods is set; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsSet(this HttpMethods methods, HttpMethods method)
		{
			return (methods & method) == method;
		}

		/// <summary>
		/// Determines whether the specified method is not set in [the specified methods].
		/// </summary>
		/// <param name="methods">The methods.</param>
		/// <param name="method">The method.</param>
		/// <returns>
		/// 	<c>true</c> if method [is not set] in [the specified methods] <c>true</c>; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsNotSet(this HttpMethods methods, HttpMethods method)
		{
			return (methods & (~method)) == 0;
		}

		/// <summary>
		/// Sets the specified methods on the given methods set.
		/// </summary>
		/// <param name="methods">The methods.</param>
		/// <param name="method">The method.</param>
		/// <returns></returns>
		public static HttpMethods Set(this HttpMethods methods, HttpMethods method)
		{
			return methods | method;
		}

		/// <summary>
		/// Clears the specified methods from the given method set
		/// </summary>
		/// <param name="methods">The methods.</param>
		/// <param name="method">The method.</param>
		/// <returns></returns>
		public static HttpMethods Clear(this HttpMethods methods, HttpMethods method)
		{
			return methods & (~method);
		}
	}
}
